package com.bjh.proxy;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.bjh.components.ApplicationContextUtil;
import com.bjh.components.ZookUtil;
import com.bjh.config.ProcessServiceAnnotationConfig;
import com.bjh.config.ZkPropertiesSource;
import com.bjh.model.InterfaceImplInfo;
import com.bjh.model.InterfaceInfo;
import com.bjh.model.ZkNodeData;
import com.bjh.model.ZkProperties;
import com.bjh.netty.client.RpcClient;
import com.bjh.netty.message.RpcMessage;
import com.bjh.req.Request;
import com.bjh.resp.Response;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@Slf4j
public class ServiceProxy<T> implements InvocationHandler, ApplicationContextAware, ApplicationRunner {


    private static final String REGISTER_PATH = "/rpc";

    private Class<T> interfaceType;

    private ZookUtil zookUtil;

    private Environment environment;

    private ZkPropertiesSource zkPropertiesSource;

    ObjectMapper objectMapper = new ObjectMapper();

    public ServiceProxy(Class<T> intefaceType) {
        this.interfaceType = interfaceType;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        if(zookUtil == null) {
            zookUtil = (ZookUtil)ApplicationContextUtil.getObject("zookUtil");
        }
        if(environment == null) {
            environment = (Environment)ApplicationContextUtil.getObject("environment");
        }
        if(zkPropertiesSource == null) {
            zkPropertiesSource = (ZkPropertiesSource)ApplicationContextUtil.getObject("zkPropertiesSource");
        }
        String nodeData = zookUtil.getNodeData(REGISTER_PATH);
        if(StrUtil.isEmpty(nodeData)) {
            throw new RuntimeException("注册中心没有数据=====");
        }
        ZkNodeData zkNodeData = objectMapper.readValue(nodeData, ZkNodeData.class);
        List<ZkProperties> zkPropertiesList = zkNodeData.getZkPropertiesList();
        for(ZkProperties zkProperties : zkPropertiesList) {
            String interfaceName = zkProperties.getInterfaceName();
            Class<?> declaringClass = method.getDeclaringClass();
            if(StringUtils.equals(declaringClass.getName(),interfaceName)) {
                List<InterfaceInfo> info = zkProperties.getInfo();
                InterfaceInfo interfaceInfo = info.get(0);
                String ipAddress = interfaceInfo.getIpAddress();
                List<InterfaceImplInfo> interfaceImplInfo = interfaceInfo.getInterfaceImplInfo();
                InterfaceImplInfo implInfo = interfaceImplInfo.get(0);

                String[] strings = ipAddress.split(":");
                //与远程Netty服务端发起连接
                RpcClient rpcClient = connNettyServer(strings[0], zkPropertiesSource.getNettyConnectPort());

                /**
                 * 封装请求参数
                 */

                //获取方法参数类型
                Class<?>[] parameterTypes = method.getParameterTypes();
                List<String> types = getTypes(parameterTypes);

                //同步调用
                result = remoteCall(method.getName(), types, args, rpcClient, implInfo, interfaceName);
                log.info("返回结果是:{}",result);
            }
        }

        Class<?> returnType = method.getReturnType();
        Object value = objectMapper.readValue(result.toString(), returnType);
        return value;
    }

    private RpcClient connNettyServer(String ipAddress,Integer port) {
        return new RpcClient(ipAddress,port);
    }

    private Object remoteCall(String methodName, List<String> argTypes, Object[] args,RpcClient rpcClient,InterfaceImplInfo implInfo,String interfaceName) throws Exception{
        RpcMessage rpcMessage = new RpcMessage();
        Request request = new Request();
        request.setId(UUID.randomUUID().toString());
        request.setMethodName(methodName);
        request.setParamsTypes(argTypes);
        if(args == null || args.length == 0) {
            request.setParams(new ArrayList<>());
        }else {
            request.setParams(parseArgs(args));
        }

        request.setInterfaceImplName(implInfo.getName());
        request.setInterfaceName(interfaceName);
        String valueAsString = objectMapper.writeValueAsString(request);
        rpcMessage.setObj(valueAsString);
        rpcMessage.setDataLength(valueAsString.getBytes(Charset.forName("utf-8")).length);
        Response result = rpcClient.sendRequest(rpcMessage);
        log.info("请求结果是：{}", JSONUtil.toJsonPrettyStr(result));
        return result.getData();
    }

    /**
     * 获取方法的参数类型
     *
     * @param methodTypes
     * @return
     */
    private List<String> getTypes(Class<?>[] methodTypes) {
        List<String> types = new ArrayList<>();
        if(methodTypes == null || methodTypes.length > 0) {
            for (int i = 0; i < methodTypes.length; i++) {
                types.add( methodTypes[i].getName());
            }
        }
        return types;
    }

    private List<Object> parseArgs(Object[] args) {
        List<Object> argsList = new ArrayList<>();
        if(args == null || args.length > 0) {
            for(int i = 0; i < args.length; i ++) {
                argsList.add(args[i]);
            }
        }
        return argsList;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {

    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("执行了run方法");
    }
}
